package com.lucifer.cloud.boot.blog.chat.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.lark.oapi.service.drive.v1.model.File;
import com.lucifer.cloud.boot.blog.chat.database.MpDocService;
import com.lucifer.cloud.boot.blog.chat.service.ChatService;
import com.lucifer.cloud.boot.blog.chat.service.DocService;
import com.lucifer.cloud.boot.blog.chat.service.domain.structured.TopN;
import com.lucifer.cloud.boot.blog.chat.service.response.DocVO;
import com.lucifer.cloud.boot.blog.chat.third.FeishuService;
import com.lucifer.cloud.boot.blog.chat.util.KeywordExtractorUtil;
import com.lucifer.cloud.boot.blog.domin.bo.BaseDoc;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.ExtractedTextFormatter;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;


import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class DocServiceImpl implements DocService {

    private final FeishuService feishuService;

    private final MpDocService mpDocService;

    @Resource
    private TokenTextSplitter tokenTextSplitter;

    @Resource(name = "pgVectorStore")
    private VectorStore vectorStore;

    @Resource
    private KeywordExtractorUtil keywordExtractorUtil;



    @Resource
    private ChatService chatService;







    @Override
    @Transactional(rollbackFor = Exception.class)
    public void loadData() {

        List<File> files = feishuService.listDocs();
        List<BaseDoc> docs = mpDocService.list(new LambdaQueryWrapper<>(BaseDoc.class).eq(BaseDoc::getSource, "feishu"));

        Map<String, BaseDoc> docMap = docs.stream().collect(Collectors.toMap(BaseDoc::getDocId, doc -> doc));

        List<BaseDoc> newDocs = files.stream().filter(v -> !docMap.containsKey(v.getToken()))
                .map(v -> BaseDoc.builder().docId(v.getToken())
                        .docName(v.getName())
                        .docType(v.getType())
                        .source("feishu")
                        .url(v.getUrl())
                        .modifiedTime(v.getModifiedTime())
                        .createdTime(new Date())
                        .updatedTime(new Date())
                        .build())
                .toList();
        addToVectorStore(newDocs);

        List<String> fileIds = files.stream().map(File::getToken).toList();
        List<BaseDoc> deletedDocs = docs.stream().filter(v -> !fileIds.contains(v.getDocId())).toList();

        if (!CollectionUtils.isEmpty(deletedDocs)) {
            removeFromVectorStore(deletedDocs);
        }

        List<BaseDoc> updatedDocs = files.stream().filter(v -> docMap.containsKey(v.getToken()))
                .filter(v -> !v.getModifiedTime().equals(docMap.get(v.getToken()).getModifiedTime()))
                .map(v -> {
                    BaseDoc baseDoc = docMap.get(v.getToken());
                    return BaseDoc.builder()
                            .id(baseDoc.getId())
                            .docId(baseDoc.getDocId())
                            .docName(v.getName())
                            .docType(v.getType())
                            .url(v.getUrl())
                            .vectorDocId(baseDoc.getVectorDocId())
                            .modifiedTime(v.getModifiedTime())
                            .updatedTime(new Date())
                            .build();

                })
                .toList();
        updateVectorStore(updatedDocs);
    }

    @Override
    public List<DocVO> list() {
        return mpDocService.list().stream().map(v -> DocVO.builder()
                .docId(v.getDocId())
                .name(v.getDocName())
                .url(v.getUrl())
                .source(v.getSource())
                .build()).toList();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveFile(MultipartFile file) {
        String vectorDocId = saveTikaDocument(file);
        Map<String, String> topN = topN2(file);
        BaseDoc doc = BaseDoc.builder().docId(vectorDocId)
                .vectorDocId(vectorDocId)
                .docName(file.getOriginalFilename() + "[热词推荐：" + topN.get("keyWordTop1") + "、" + topN.get("keyWordTop2") + "、" + topN.get("keyWordTop3") + "]")
                .source("upload")
                .docType(file.getContentType())
                .modifiedTime(String.valueOf(System.currentTimeMillis()))
                .createdTime(new Date())
                .updatedTime(new Date())
                .build();
       return mpDocService.save(doc);
    }



    @SneakyThrows
    private String saveTikaDocument(MultipartFile file) {
        String filename = file.getOriginalFilename();
        String name = StringUtils.substringBefore(filename, ".");
        String contentType = file.getContentType();
        TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(file.getResource());
        List<Document> documents = tikaDocumentReader.read();
        List<Document> applyDocuments = tokenTextSplitter.apply(documents);
        List<Document> writeDocuments = Lists.newArrayList();
        for (Document document : applyDocuments) {
            Map<String, Object> metadata = document.getMetadata();
            String text = document.getText();
            //List<String> topN = keywordExtractorUtil.loadTopKeywords(text, 3);
            TopN topN = chatService.topN("获取这个文本出现前三的热词", text);
            metadata.put("id", document.getId());
            metadata.put("name", name);
            metadata.put("contentType", contentType);
            metadata.put("keyWordTop1", topN.keyWordTop1());
            metadata.put("keyWordTop2", topN.keyWordTop2());
            metadata.put("keyWordTop3", topN.keyWordTop3());
            Document writeDocument = Document.builder()
                    .id(document.getId())
                    .text(text)
                    .media(document.getMedia())
                    .metadata(document.getMetadata())
                    .score(document.getScore())
                    .build();
            writeDocuments.add(writeDocument);
        }
        vectorStore.accept(writeDocuments);
        return documents.get(0).getId();
    }




    private Map<String, String> topN2(MultipartFile file) {
        TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(file.getResource());
        List<Document> documents = tikaDocumentReader.read();
        Map<@Nullable String, @Nullable String> topMap = Maps.newLinkedHashMap();
        TopN topN = null;
        for (Document document : documents) {
            String text = document.getText();
            topN = chatService.topN("获取这个文本出现前三的热词", text);
        }
        topMap.put("keyWordTop1",topN.keyWordTop1());
        topMap.put("keyWordTop2",topN.keyWordTop2());
        topMap.put("keyWordTop3",topN.keyWordTop3());
        return topMap;
    }



    private Map< String, String> topN(MultipartFile file) {
        TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(file.getResource());
        List<Document> documents = tikaDocumentReader.read();
        Map<@Nullable String, @Nullable String> topMap = Maps.newLinkedHashMap();
        for (Document document : documents) {
            String text = document.getText();
            List<String> topN = keywordExtractorUtil.loadTopKeywords(text, 3);
            topMap.put("keyWordTop1", topN.get(0));
            topMap.put("keyWordTop2", topN.get(1));
            topMap.put("keyWordTop3", topN.get(2));
        }
        return topMap;
    }


    @SneakyThrows
    public void savePdfFile(MultipartFile file) {
        // 获取文件名
        String fileName = file.getOriginalFilename();
        // 获取文件内容类型
        String contentType = file.getContentType();
        // 获取文件字节数组
        byte[] bytes = file.getBytes();
        // 创建一个临时文件
        Path tempFile = Files.createTempFile("temp-", fileName);
        // 将文件字节数组保存到临时文件
        Files.write(tempFile, bytes);
        // 创建一个 FileSystemResource 对象
        org.springframework.core.io.Resource fileResource = new FileSystemResource(tempFile.toFile());
        PdfDocumentReaderConfig loadConfig = PdfDocumentReaderConfig.builder()
                .withPageExtractedTextFormatter(
                        new ExtractedTextFormatter
                                .Builder()
                                .withNumberOfBottomTextLinesToDelete(3)
                                .withNumberOfTopPagesToSkipBeforeDelete(1)
                                .build()
                )
                .withPagesPerDocument(1)
                .build();

        PagePdfDocumentReader pagePdfDocumentReader = new PagePdfDocumentReader(fileResource, loadConfig);
        vectorStore.accept(tokenTextSplitter.apply(pagePdfDocumentReader.get()));
    }


    private void addToVectorStore(List<BaseDoc> docs) {
        for (BaseDoc doc : docs) {
            Document document = null;
            String text = feishuService.readDoc(doc.getDocId());
            List<String> topN = keywordExtractorUtil.loadTopKeywords(text, 3);
            Map<String, Object> metadata = new HashMap<>();
            metadata.put("id",doc.getDocId());
            metadata.put("name",doc.getDocName());
            metadata.put("source",doc.getDocName());
            metadata.put("contentType",doc.getDocType());
            metadata.put("keyWordTop1", topN.get(0));
            metadata.put("keyWordTop2", topN.get(1));
            metadata.put("keyWordTop3", topN.get(2));
            // TODO 后期策略模式.
            if(Objects.equals("docx",doc.getDocType())){
                document = new Document(text, metadata);
            }else if(Objects.equals("file",doc.getDocType())){
                document = new Document(text, metadata);
            }
            vectorStore.write(List.of(document));
            doc.setVectorDocId(document.getId());
        }
        mpDocService.saveBatch(docs);
    }

    private void updateVectorStore(List<BaseDoc> updatedDocs) {
        removeFromVectorStore(updatedDocs);
        addToVectorStore(updatedDocs);
        mpDocService.updateBatchById(updatedDocs);
    }

    private void removeFromVectorStore(List<BaseDoc> docs) {
        if (CollectionUtils.isEmpty(docs)) {
            return;
        }
        vectorStore.delete(docs.stream().map(BaseDoc::getVectorDocId).toList());
        mpDocService.remove(
                new LambdaQueryWrapper<BaseDoc>().in(BaseDoc::getDocId, docs.stream().map(BaseDoc::getDocId).toList()));
    }
}
